#ifndef __CINETIC__DELEGATE_H__
#define __CINETIC__DELEGATE_H__

#include <utility>
#include <vector>

// As seen in http://molecularmusings.wordpress.com/2011/09/19/generic-type-safe-delegates-and-events-in-c/

#define JOIN(x,y) JOIN2(x,y)
#define JOIN2(x,y) x##y

#define TO_BOOL_0 0
#define TO_BOOL_1 1
#define TO_BOOL_2 1
#define TO_BOOL_3 1
#define TO_BOOL_4 1
#define TO_BOOL_5 1
#define TO_BOOL_6 1
#define TO_BOOL_7 1
#define TO_BOOL_8 1
#define TO_BOOL(x) JOIN(TO_BOOL_,x)

#define COMMA_MARK_0
#define COMMA_MARK_1 ,
#define COMMA_IF(cond) JOIN(COMMA_MARK_,TO_BOOL(cond))

#define COMMA_LIST_0(elem) 
#define COMMA_LIST_1(elem) elem(1)
#define COMMA_LIST_2(elem) COMMA_LIST_1(elem)##, elem(2)
#define COMMA_LIST_3(elem) COMMA_LIST_2(elem)##, elem(3)
#define COMMA_LIST_4(elem) COMMA_LIST_3(elem)##, elem(4)
#define COMMA_LIST_5(elem) COMMA_LIST_4(elem)##, elem(5)
#define COMMA_LIST_6(elem) COMMA_LIST_5(elem)##, elem(6)
#define COMMA_LIST_7(elem) COMMA_LIST_6(elem)##, elem(7)
#define COMMA_LIST_8(elem) COMMA_LIST_7(elem)##, elem(8)
#define COMMA_LIST(count,elem) COMMA_LIST_##count(elem)

#define LIST_0(elem) 
#define LIST_1(elem) elem(1)
#define LIST_2(elem) LIST_1(elem) elem(2)
#define LIST_3(elem) LIST_2(elem) elem(3)
#define LIST_4(elem) LIST_3(elem) elem(4)
#define LIST_5(elem) LIST_4(elem) elem(5)
#define LIST_6(elem) LIST_5(elem) elem(6)
#define LIST_7(elem) LIST_6(elem) elem(7)
#define LIST_8(elem) LIST_7(elem) elem(8)
#define LIST(count,elem) LIST_##count(elem)

#define JOINER_ELEMENT(x) x 
#define TEMPLATE_LIST_ELEMENT(x) typename P##x
#define FUNC_POINTER_LIST_ELEMENT(x) P##x
#define PARAMETER_LIST_ELEMENT(x) P##x p##x
#define ARGUMENT_LIST_ELEMENT(x) p##x

#define DELEGATE_TEMPLATE_PARAMETER_LIST(x) COMMA_LIST(x,TEMPLATE_LIST_ELEMENT) 
#define FUNC_POINTER_PARAMETER_LIST(x) COMMA_LIST(x,FUNC_POINTER_LIST_ELEMENT) 
#define PARAMETER_LIST(x) COMMA_LIST(x,PARAMETER_LIST_ELEMENT) 
#define ARGUMENT_LIST(x) COMMA_LIST(x,ARGUMENT_LIST_ELEMENT) 

#define DELEGATE_IMPL(x) \
	template<typename R COMMA_IF(x) DELEGATE_TEMPLATE_PARAMETER_LIST(x)> \
	class Delegate<R (FUNC_POINTER_PARAMETER_LIST(x))> \
	{ \
		typedef void* InstancePtr; \
		typedef R (*InternalFunction)(InstancePtr COMMA_IF(x) FUNC_POINTER_PARAMETER_LIST(x)); \
		typedef std::pair<InstancePtr, InternalFunction> Stub; \
		template <R (*Function)(FUNC_POINTER_PARAMETER_LIST(x))> \
		static __forceinline R FunctionStub(InstancePtr COMMA_IF(x) PARAMETER_LIST(x) ) \
		{ \
			return (Function)(ARGUMENT_LIST(x)); \
		} \
		template <class C, R (C::*Function)(FUNC_POINTER_PARAMETER_LIST(x))> \
		static __forceinline R ClassMethodStub(InstancePtr instance COMMA_IF(x) PARAMETER_LIST(x)) \
		{ \
			return (static_cast<C*>(instance)->*Function)(ARGUMENT_LIST(x)); \
		} \
    public: \
        class Event \
        { \
        public: \
            void operator+=( const Delegate& d ) \
            { \
                m_listeners.push_back(d); \
            } \
            void operator-=( const Delegate& d ) \
            { \
                auto it = std::find(m_listeners.begin(), m_listeners.end(), d); \
                if (it != m_listeners.end()) { \
                    m_listeners.erase(it); \
                } \
            } \
            void operator()(PARAMETER_LIST(x)) \
            { \
                for (auto it = m_listeners.begin(); it != m_listeners.end(); it++) { \
                    it->Invoke(ARGUMENT_LIST(x)); \
                } \
            } \
        private: \
            std::vector<Delegate>   m_listeners; \
        }; \
	public: \
		Delegate(void) : m_stub(nullptr, nullptr) {} \
		template <R (*Function)(FUNC_POINTER_PARAMETER_LIST(x))> \
		void Bind(void) \
		{ \
			m_stub.first = nullptr; \
			m_stub.second = &FunctionStub<Function>; \
		} \
		template <class C, R (C::*Function)(FUNC_POINTER_PARAMETER_LIST(x))> \
		void Bind(C* instance) \
		{ \
			m_stub.first = instance; \
			m_stub.second = &ClassMethodStub<C, Function>; \
		} \
		R Invoke(PARAMETER_LIST(x)) const \
		{ \
            ME_ASSERT(m_stub.second != nullptr, "Cannot invoke unbound delegate. Call Bind() first."); \
			return m_stub.second(m_stub.first COMMA_IF(x) ARGUMENT_LIST(x)); \
		} \
        bool operator==( const Delegate& d ) \
        { \
            return d.m_stub == m_stub; \
        } \
	private: \
		Stub m_stub; \
	}; 

#define DELEGATE_IMPL_LIST(x) LIST(x, DELEGATE_IMPL)

namespace Cinetic
{
	template <typename T>
	class Delegate {};

	DELEGATE_IMPL(0)
	DELEGATE_IMPL_LIST(8)
}

#endif